package it.unibo.refolding.common;

import it.unimi.dsi.fastutil.doubles.Double2IntAVLTreeMap;
import it.unimi.dsi.fastutil.doubles.Double2IntSortedMap;

import java.util.logging.Logger;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;

public class MovingWindows {

  @SuppressWarnings("unused")
  private static final Logger logger = Logger.getLogger(MovingWindows.class.getSimpleName());

  static void movingWindow(
    double[] a, int windowSize, boolean partialHeads, boolean partialTails,
    Callback<WindowFrequencyFunctionInput> f) {
    Preconditions.checkArgument(windowSize >= 0, windowSize);
    // Not much difference between Double2IntAVLTreeMap and Double2IntRBTreeMap.
    Double2IntSortedMap sortedMap = new Double2IntAVLTreeMap();
    for (int i = 0, j = 0; i < a.length;) {
      /*
       * TODO: I've seen an assertion fail here close to beginning for
       * acetate50mM_pH3.5/20130119a. Could it be that windowSize was negative,
       * since the check above was not there yet?
       */
      assert j - i <= windowSize;
      assert j >= i;
      if (j - i == windowSize | (partialHeads & i == 0) | (partialTails & j == a.length)) {
        f.call(new WindowFrequencyFunctionInput(i, j, sortedMap));
      }
      if (j != a.length) {
        CommonThings.addOrIncrementValueForKey(a[j], sortedMap);
      }
      if (j - i == windowSize | j == a.length) {
        CommonThings.removeOrDecrementValueForKey(a[i++], sortedMap);
      }
      if (j != a.length) {
        ++j;
      }
    }
  }

  public static void rightDeflectionWindow(
    final double[] deflection, int windowSize, final boolean[] potentialPeaks,
    boolean partialTails, final Function<PointFrequencyFunctionInput, Boolean> f) {
    movingWindow(
      deflection, windowSize, false, partialTails, new Callback<WindowFrequencyFunctionInput>() {
        @Override
        public void call(WindowFrequencyFunctionInput fi) {
          PointFrequencyFunctionInput.right(deflection, potentialPeaks, fi, f);
        }
      });
  }

  public static void centerDeflectionWindow(
    final double[] deflection, int windowSize, final boolean[] potentialPeaks,
    final Function<PointFrequencyFunctionInput, Boolean> f) {
    movingWindow(
      deflection, windowSize, false, false, new Callback<WindowFrequencyFunctionInput>() {
        @Override
        public void call(WindowFrequencyFunctionInput fi) {
          PointFrequencyFunctionInput.center(deflection, potentialPeaks, fi, f);
        }
      });
  }

  public static void leftDeflectionWindow(
    final double[] deflection, int windowSize, final boolean[] potentialPeaks,
    final Function<PointFrequencyFunctionInput, Boolean> f) {
    movingWindow(
      deflection, windowSize, false, false, new Callback<WindowFrequencyFunctionInput>() {
        @Override
        public void call(WindowFrequencyFunctionInput fi) {
          PointFrequencyFunctionInput.left(deflection, potentialPeaks, fi, f);
        }
      });
  }
}
